#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <glib.h>

#include "jsonrpc.h"
#include "util.h"

void jsonrpc_register_method(jsonrpc_server *server, const char *name, jsonrpc_method_ptr method) {
	g_hash_table_insert(server->methods, g_strdup(name), method);
}

static json_object *jsonrpc_error(json_object *id, int code, const char *message) {
	json_object *result = json_object_new_object();
	json_object *error = json_object_new_object();
	json_object_object_add(result, "jsonrpc", json_object_new_string(JSONRPC_VERSION));
	json_object_object_add(result, "id", id ? json_object_get(id) : NULL);

	json_object_object_add(error, "code", json_object_new_int(code));
	json_object_object_add(error, "message", json_object_new_string(message));

	json_object_object_add(result, "error", error);

	return result;
}

static json_object *invoke(jsonrpc_server *server, json_object *obj) {
	json_object *id = json_object_object_get(obj, "id");
	json_object *jsonrpc = json_object_object_get(obj, "jsonrpc");
	json_object *method = json_object_object_get(obj, "method");
	json_object *params = json_object_object_get(obj, "params");

	json_object *result = NULL;

	if (jsonrpc && method && params) {
		json_type type = json_object_get_type(params);
		if (type == json_type_object || type == json_type_string || type == json_type_array) {
			const char *methodname = json_object_get_string(method);
			gpointer ptr = g_hash_table_lookup(server->methods, methodname);
			if (ptr) {
				jsonrpc_method_ptr func = (jsonrpc_method_ptr) ptr;
				json_object *ret = func(params);
				result = json_object_new_object();
				json_object_object_add(result, "jsonrpc", json_object_new_string(JSONRPC_VERSION));
				json_object_object_add(result, "id", json_object_get(id));
				json_object_object_add(result, "result", ret);
			} else {
				result = jsonrpc_error(id, JSONRPC_METHOD_NOT_FOUND, JSONRPC_MESSAGE_METHOD_NOT_FOUND);
			}
		} else {
			result = jsonrpc_error(NULL, JSONRPC_INVALID_REQUEST, JSONRPC_MESSAGE_INVALID_REQUEST);
		}
	} else {
		result = jsonrpc_error(NULL, JSONRPC_INVALID_REQUEST, JSONRPC_MESSAGE_INVALID_REQUEST);
	}

	return result;
}

json_object *jsonrpc_invoke(jsonrpc_server *server, const char *json) {
	json_object *obj = json_tokener_parse(json);

	json_object *result = NULL;
	if (obj) {
		switch (json_object_get_type(obj)) {
		case json_type_object: {
			result = invoke(server, obj);
			break;
		}

		case json_type_array: {
			int i = 0;
			int size = json_object_array_length(obj);
			result = json_object_new_array();
			for (i = 0; i < size; i++) {
				json_object *item = json_object_array_get_idx(obj, i);
				if (json_object_get_type(item) == json_type_object) {
					json_object_array_add(result, invoke(server, item));
				} else {
					json_object_array_add(result, jsonrpc_error(NULL, JSONRPC_INVALID_REQUEST, JSONRPC_MESSAGE_INVALID_REQUEST));
				}
			}
			break;
		}

		default:
			break;
		}

		json_object_put(obj);
	} else {
		result = jsonrpc_error(NULL, JSONRPC_PARSE_ERROR, JSONRPC_MESSAGE_PARSE_ERROR);
	}

	return result;
}
